/**********************************************************************************
Copyright (C), by AppleCai
Project                      : Study Kernel
Description                  : BSP level for cdev
History                      :
CPU and Compiler             : AM335x,ARM-LINIX-GCC
GenDate                      : 2020-10-25
OwnerBy                      : Apple Cai
**********************************************************************************/
#if 0
#include <linux/module.h>  
#include <linux/init.h>  
#include <linux/types.h>  
#include <linux/fs.h>  
#include <linux/errno.h>  
#include <linux/mm.h>  
#include <linux/sched.h>  
#include <linux/cdev.h>  
#include <linux/slab.h>
#include <asm/io.h>  
#include <linux/version.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 3, 0)
        #include <asm/switch_to.h>
#else
        #include <asm/system.h>
#endif
#include <linux/uaccess.h>  
#endif
#include <linux/module.h>  
#include <linux/init.h>  
#include <linux/cdev.h>  
#include <linux/fs.h>  
#include <linux/slab.h>
#include <linux/uaccess.h> 
#include <asm-generic/ioctl.h>
#include "applepaper_cmd.h"
#define APPLEPAPER_SIZE 0x1000 /*全局内存最大4KB*/  
#define APPLEPAPERMAJOR 254  /* 预设的applepaper 的主设备号 */
#define DEV_NUM	  3  /* 创建3个设备*/
#define START_DEV  6 /* 次设备号从6开始 */  

#define FILE_LOCK  0 /* 打开file open/close等级的锁 */
#define FILEOP_LOCK  1/* 打开file open/close等级的锁 */
static int applepaper_major = APPLEPAPERMAJOR;/*预设的applepaper的主设备号*/  
static struct class *apple_class;
  
/*applepaper的设备结构体：包含了对应于applepaper字符设备的cdev 和 使用内存mem[APPLEPAPER_SIZE]*/  
struct applepaper_dev  
{  
 struct cdev cdev;  //cdev结构体  
 unsigned char mem[APPLEPAPER_SIZE];  //全局内存  
 struct mutex mulock;
};  
  
struct applepaper_dev *applepaper_devp;  //设备结构体指针  
  
/*文件打开函数*/  
int applepaper_open(struct inode *inode,struct file *filp)  
{  
 struct applepaper_dev *dev = container_of(inode->i_cdev,struct applepaper_dev,cdev);
 filp->private_data = dev; //将设备结构体指针赋值给文件私有数据指针  
 #if FILE_LOCK
 if (mutex_lock_interruptible(&dev->mulock)) {  /* 只能打开一个文件进行操作 */
	 return -ERESTARTSYS;
 }
 #endif
 return 0;  
}  
  
/*文件释放函数*/  
int applepaper_release(struct inode *inode,struct file *filp)  
{  
 #if FILE_LOCK
 struct applepaper_dev *dev = container_of(inode->i_cdev,struct applepaper_dev,cdev);
 filp->private_data = dev;
 mutex_unlock(&dev->mulock);
 #endif
 return 0;  
}  

/*设备控制函数：*/ 
static long applepaper_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{  
 struct applepaper_dev *dev = filp->private_data; //获得设备结构体指针  
if(_IOC_TYPE(cmd) != APPLEPAPER_MAGIC) return -EINVAL;
if(_IOC_NR(cmd) > APPLEPAPER_MAX_NR) return -EINVAL;
 switch(cmd)  
 {  
  case  APPLEPAPER_CLEAR: 
 #if FILEOP_LOCK
 mutex_lock(&dev->mulock); 
 #endif  
   memset(dev->mem,0,APPLEPAPER_SIZE);  
 #if FILEOP_LOCK
 mutex_unlock(&dev->mulock); 
 #endif 
   filp->f_pos = 0;  
   printk(KERN_INFO"applepaper is set to zero\n");  
   break;  
  default:  
   return -EINVAL;  
 }  
 
 return 0;  
}  
/*读函数：读写函数主要是让设备结构体的mem[]数组与用户空间交互数据，并随着访问字节数变更返回用户的文件读写偏移位置*/  
static ssize_t applepaper_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)  
{  
 //unsigned long p =  filp->f_pos;//*ppos; 
 unsigned long p =  *ppos;  
 unsigned int count = size;  
 int ret = 0;  
 struct applepaper_dev *dev = filp->private_data; //获得设备结构体指针  
   
 if(p >= APPLEPAPER_SIZE)   //分析和获取有效的写长度  
 {  
  return count ? -ENXIO:0;  //看看man page里对ENXIO的解释
 }  
 if(count > APPLEPAPER_SIZE-p)  
 {  
  count = APPLEPAPER_SIZE-p;  
 }  
 #if FILEOP_LOCK
 mutex_lock(&dev->mulock); 
 #endif
 if(copy_to_user(buf,(void *)(dev->mem+p),count))  //内核空间->用户空间  
 {  
  ret = -EFAULT;  
 }  
 else  
 {    
  ret = count;
  //filp->f_pos += count; 
  *ppos+=count;  
  printk(KERN_INFO"read %d bytes(s) from %ld\n",count,p);  
 } 
 #if FILEOP_LOCK
 mutex_unlock(&dev->mulock); 
 #endif 
 return ret;  
}  
  
/*写函数*/  
static ssize_t applepaper_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)  
{  
 //unsigned long p = filp->f_pos;//*ppos;  
 unsigned long p = *ppos;
 unsigned int count = size;  
 int ret;  
   
 struct applepaper_dev *dev = filp->private_data;  
   
 if(p >= APPLEPAPER_SIZE)  //分析和获取有效的写长度  
 {  
  return count? -ENXIO:0;  
 }  
   
 if(count > APPLEPAPER_SIZE-p)  // 要写的字节数太多 
 {  
  count = APPLEPAPER_SIZE-p;  
 }  
 #if FILEOP_LOCK
 mutex_lock(&dev->mulock); 
 #endif   
 if(copy_from_user(dev->mem + p,buf,count)) // 用户空间->内核空间  
 {  
  ret = -EFAULT;  
 }  
 else  
 {  
  //filp->f_pos += count;  
  *ppos+=count;  
  ret = count;  
  printk(KERN_INFO"written %d bytes(s) from %ld\n",count,p);  
 }  
 #if FILEOP_LOCK
 mutex_unlock(&dev->mulock); 
 #endif 
 return ret;  
}  
/*seek文件定位函数：seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/  
static loff_t applepaper_llseek(struct file *filp,loff_t offset,int orig)  
{  
 loff_t ret = 0;  
 switch(orig)  
 {  
  case APPLEPAPER_BEGIN_POS:   //相对文件开始位置偏移  
   if(offset <0 )  
   {  
    ret = -EINVAL;  
    break;  
   }  
     
   if((unsigned int )offset > APPLEPAPER_SIZE)  
   {  
    ret = - EINVAL;  
    break;  
   }  
   filp->f_pos = (unsigned int)offset;  
   ret = filp->f_pos;  
   break;  
     
  case APPLEPAPER_CUR_POS:   //相对文件当前位置偏移  
   if((filp->f_pos + offset) > APPLEPAPER_SIZE)  
   {  
    ret = -EINVAL;  
    break;  
   }  
   if((filp->f_pos + offset)<0)  
   {  
    ret = -EINVAL;  
    break;  
   }  
   filp->f_pos +=offset;  
   ret = filp->f_pos;  
   break;  
  default:  
   ret = -EINVAL;  
   break;   
 } 

 printk("filp->f_pos=%lld",filp->f_pos);
 return ret;  
}  
/*文件操作结构体*/  
static const struct file_operations applepaper_fops=  
{  
 .owner = THIS_MODULE,
 .llseek = applepaper_llseek,
 .read = applepaper_read,
 .write = applepaper_write,
 .unlocked_ioctl = applepaper_ioctl,  
 .open = applepaper_open,  
 .release = applepaper_release,
};  
  

static int applepaper_setup_cdev(struct applepaper_dev *dev,int index)
{
  int result,devno = MKDEV(applepaper_major,index);
  /* initialze for mutex */
  mutex_init(&dev->mulock);
  cdev_init(&dev->cdev,&applepaper_fops);
  dev->cdev.owner = THIS_MODULE;
  dev->cdev.ops = &applepaper_fops;
  result = cdev_add(&dev->cdev,devno,1);
  if(result<0)
  {
	  printk("Error %d adding applepaper%d\n",result,index);
  	goto fail_dev;
  }
  fail_dev:unregister_chrdev_region(devno,DEV_NUM); 
	return result; 
}	
/*设备驱动模块加载函数*/  
static int __init applepaper_init(void)  
{  
 int result,i;  
 struct device *apple_device;
 dev_t devno = MKDEV(applepaper_major,START_DEV);  
   

  result = alloc_chrdev_region(&devno,START_DEV,DEV_NUM,"applepaper");  
  applepaper_major = MAJOR(devno);  

   
 if(result < 0)  
 {  
  return result;  
 }  
   
 applepaper_devp = kzalloc(DEV_NUM*sizeof(struct applepaper_dev),GFP_KERNEL); //申请设备结构体的内存  
 if(!applepaper_devp)  
 {  
  result = -ENOMEM;  
  goto fail_malloc;  
 }  

 for(i=0;i<DEV_NUM;i++)
  applepaper_setup_cdev(applepaper_devp+i,START_DEV+i);

/* 在/sysfs文件夹下创建类*/
 apple_class = class_create(THIS_MODULE, "apple_class"); //创建my_class
 if(IS_ERR(apple_class))
 {
  printk("Err: failed in creating class.\n");
  return -1;
 }
 for(i=0;i<DEV_NUM;i++)
 {
 /* 创建设备节点文件 */
 apple_device = device_create( apple_class, NULL, MKDEV(applepaper_major, START_DEV+i),NULL,"applepaper%d", START_DEV+i ); 
 if (IS_ERR(apple_device)) {
	result = PTR_ERR(apple_device);
    printk("err device_create");
    goto device_err;
 }
 else
 {
	printk (KERN_INFO "Registered character driver\n");
 }
 }
 return 0;
   
fail_malloc:unregister_chrdev_region(devno,DEV_NUM); 
	return result; 
device_err:     
    class_destroy(apple_class); 
 	return result;  
}  
  
/*模块卸载函数*/  
void __exit applepaper_exit(void)  
{  
 int i;
 for(i=0;i<DEV_NUM;i++)
	cdev_del(&(applepaper_devp+i)->cdev); //注销cdev  
 kfree(applepaper_devp);          //释放设备结构体内存  
 for(i=0;i<DEV_NUM;i++)
  device_destroy(apple_class, MKDEV(applepaper_major, START_DEV+i));   // 删除设备节点
 class_destroy(apple_class);   // 删除设备类
 unregister_chrdev_region(MKDEV(applepaper_major,START_DEV),DEV_NUM); //释放设备号  
 printk("applepaper exit!\n");
}  
  
MODULE_AUTHOR("AppleCai");  
MODULE_LICENSE("GPL");  
 
module_init(applepaper_init);  
module_exit(applepaper_exit);   